<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  

  
  <title>Javascript的尾递归及其优化 | 顾伊凡</title>
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
  <meta name="description" content="在平时的代码里，递归是很常见的，然而它可能会带来的调用栈溢出问题有时也令人头疼：我们知道， js 引擎（包括大部分语言）对于函数调用栈的大小是有限制的，如下图（虽然都是很老的浏览器，但还是有参考价值）：为了解决递归时调用栈溢出的问题，除了把递归函数改为迭代的形式外，改为尾递归的形式也可以解决（虽然目前大部分浏览器没有对尾递归（尾调用）做优化，依然会导致栈溢出，但了解尾递归的优化方式还是有价值的。而">
<meta name="keywords" content="尾递归,尾调用,trampoline,递归,迭代">
<meta property="og:type" content="article">
<meta property="og:title" content="Javascript的尾递归及其优化">
<meta property="og:url" content="http://yoursite.com/2018/10/17/Javascript的尾递归及其优化/index.html">
<meta property="og:site_name" content="顾伊凡">
<meta property="og:description" content="在平时的代码里，递归是很常见的，然而它可能会带来的调用栈溢出问题有时也令人头疼：我们知道， js 引擎（包括大部分语言）对于函数调用栈的大小是有限制的，如下图（虽然都是很老的浏览器，但还是有参考价值）：为了解决递归时调用栈溢出的问题，除了把递归函数改为迭代的形式外，改为尾递归的形式也可以解决（虽然目前大部分浏览器没有对尾递归（尾调用）做优化，依然会导致栈溢出，但了解尾递归的优化方式还是有价值的。而">
<meta property="og:locale" content="default">
<meta property="og:image" content="http://yoursite.com/2018/10/17/Javascript的尾递归及其优化/overflow.png">
<meta property="og:image" content="http://yoursite.com/2018/10/17/Javascript的尾递归及其优化/browsers-overflow.png">
<meta property="og:image" content="http://yoursite.com/2018/10/17/Javascript的尾递归及其优化/callWithoutOpt.png">
<meta property="og:image" content="http://yoursite.com/2018/10/17/Javascript的尾递归及其优化/callWithOpt.png">
<meta property="og:image" content="http://yoursite.com/2018/10/17/Javascript的尾递归及其优化/browserSupport.png">
<meta property="og:updated_time" content="2018-10-18T09:12:12.000Z">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="Javascript的尾递归及其优化">
<meta name="twitter:description" content="在平时的代码里，递归是很常见的，然而它可能会带来的调用栈溢出问题有时也令人头疼：我们知道， js 引擎（包括大部分语言）对于函数调用栈的大小是有限制的，如下图（虽然都是很老的浏览器，但还是有参考价值）：为了解决递归时调用栈溢出的问题，除了把递归函数改为迭代的形式外，改为尾递归的形式也可以解决（虽然目前大部分浏览器没有对尾递归（尾调用）做优化，依然会导致栈溢出，但了解尾递归的优化方式还是有价值的。而">
<meta name="twitter:image" content="http://yoursite.com/2018/10/17/Javascript的尾递归及其优化/overflow.png">
  
    <link rel="alternate" href="/atom.xml" title="顾伊凡" type="application/atom+xml">
  
  
    <link rel="icon" href="/favicon.png">
  
  
    <link href="//fonts.googleapis.com/css?family=Source+Code+Pro" rel="stylesheet" type="text/css">
  
  <link rel="stylesheet" href="/css/style.css">
</head>

<body>
  <div id="container">
    <div id="wrap">
      <header id="header">
  <div id="banner"></div>
  <div id="header-outer" class="outer">
    <div id="header-title" class="inner">
      <h1 id="logo-wrap">
        <a href="/" id="logo">顾伊凡</a>
      </h1>
      
    </div>
    <div id="header-inner" class="inner">
      <nav id="main-nav">
        <a id="main-nav-toggle" class="nav-icon"></a>
        
          <a class="main-nav-link" href="/">Home</a>
        
          <a class="main-nav-link" href="/archives">Archives</a>
        
      </nav>
      <nav id="sub-nav">
        
          <a id="nav-rss-link" class="nav-icon" href="/atom.xml" title="RSS Feed"></a>
        
        <a id="nav-search-btn" class="nav-icon" title="Search"></a>
      </nav>
      <div id="search-form-wrap">
        <form action="//google.com/search" method="get" accept-charset="UTF-8" class="search-form"><input type="search" name="q" class="search-form-input" placeholder="Search"><button type="submit" class="search-form-submit">&#xF002;</button><input type="hidden" name="sitesearch" value="http://yoursite.com"></form>
      </div>
    </div>
  </div>
</header>
      <div class="outer">
        <section id="main"><article id="post-Javascript的尾递归及其优化" class="article article-type-post" itemscope itemprop="blogPost">
  <div class="article-meta">
    <a href="/2018/10/17/Javascript的尾递归及其优化/" class="article-date">
  <time datetime="2018-10-17T06:01:47.000Z" itemprop="datePublished">2018-10-17</time>
</a>
    
  </div>
  <div class="article-inner">
    
    
      <header class="article-header">
        
  
    <h1 class="article-title" itemprop="name">
      Javascript的尾递归及其优化
    </h1>
  

      </header>
    
    <div class="article-entry" itemprop="articleBody">
      
        <p>在平时的代码里，递归是很常见的，然而它可能会带来的调用栈溢出问题有时也令人头疼：<br><img src="/2018/10/17/Javascript的尾递归及其优化/overflow.png" width="600"><br>我们知道， js 引擎（包括大部分语言）对于函数调用栈的大小是有限制的，如下图（虽然都是很老的浏览器，但还是有参考价值）：<br><img src="/2018/10/17/Javascript的尾递归及其优化/browsers-overflow.png" width="600"><br>为了解决递归时调用栈溢出的问题，除了把递归函数改为迭代的形式外，改为<code>尾递归</code>的形式也可以解决（虽然目前大部分浏览器没有对尾递归（尾调用）做优化，依然会导致栈溢出，但了解尾递归的优化方式还是有价值的。而且我们可以通过一个统一的工具函数把尾递归转化为不会溢出的形式，这些下文会一一展开）。<br>在讨论<code>尾递归</code>之前，我们先了解一下<code>尾调用</code>，以及 js 引擎如何对其进行优化。</p>
<h1 id="尾调用"><a href="#尾调用" class="headerlink" title="尾调用"></a>尾调用</h1><p>当函数<code>a</code>的最后一个动作是调用函数<code>b</code>时，那么对函数<code>b</code>的调用形式就是<code>尾调用</code>。比如下面的代码里对<code>fn1</code>的调用就是尾调用：<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> fn1 = <span class="function">(<span class="params">a</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">let</span> b = a + <span class="number">1</span>;</span><br><span class="line">  <span class="keyword">return</span> b;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> fn2 = <span class="function">(<span class="params">x</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">let</span> y = x + <span class="number">1</span>;</span><br><span class="line">  <span class="keyword">return</span> fn1(y);        <span class="comment">// line A</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> result = fn2(<span class="number">1</span>);  <span class="comment">// line B</span></span><br></pre></td></tr></table></figure><br>我们知道，在代码执行时，会产生一个调用栈，调用某个函数时会将其压入栈，当它 return 后就会出栈，下图是对于这段代码简易示例的调用栈（没有对<code>尾调用</code>做优化）：<br><img src="/2018/10/17/Javascript的尾递归及其优化/callWithoutOpt.png" width="600"><br>首先<code>fn2</code>被压入栈，<code>x</code>、<code>y</code>依次被创建并赋值，栈内也会记录相应的信息，同时也记录了该函数被调用的地方，这样在函数 return 后就能知道结果应该返回到哪里。然后<code>fn1</code>入栈，当它运行结束后就可以出栈，之后<code>fn2</code>也得到了想要的结果，返回结果后也出栈，此段代码运行结束。<br>仔细看一下以上过程，你有没有觉得第二第三步中<code>fn2</code>的存在有些多余？它内部的一切计算都已经完成了，此时它在栈内的唯一作用就是记录最后结果应该返回到哪一行。因而可以有如下的优化：<br><img src="/2018/10/17/Javascript的尾递归及其优化/callWithOpt.png" width="600"><br>在第二步调用<code>fn1</code>时，<code>fn2</code>即可出栈，并把<code>line B</code>信息给<code>fn1</code>，然后将<code>fn1</code>入栈，最后把<code>fn1</code>的结果返回到<code>line B</code>即可，这样就减小了调用栈的大小。</p>
<h1 id="辨别是否是尾调用"><a href="#辨别是否是尾调用" class="headerlink" title="辨别是否是尾调用"></a>辨别是否是尾调用</h1><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> a = <span class="function"><span class="params">()</span> =&gt;</span> &#123;</span><br><span class="line">  b();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>这里<code>b</code>的调用不是尾调用，因为函数<code>a</code>在调用<code>b</code>后还隐式地执行了一段<code>return undefined</code>，如下面这段代码：<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> a = <span class="function"><span class="params">()</span> =&gt;</span> &#123;</span><br><span class="line">  b();</span><br><span class="line">  <span class="keyword">return</span> <span class="literal">undefined</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><br>如果我们把它当做<code>尾调用</code>并按照上面的方法优化的话，就得不到函数<code>a</code>正确的返回结果了。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> a = <span class="function"><span class="params">()</span> =&gt;</span> b() || c();</span><br><span class="line"><span class="keyword">const</span> a1 = <span class="function"><span class="params">()</span> =&gt;</span> b() &amp;&amp; c();</span><br></pre></td></tr></table></figure>
<p>这里<code>a</code>和<code>a1</code>中的<code>b</code>都不是<code>尾调用</code>，因为在它调用之后还有判断的动作以及可能的对于<code>c</code>的调用，而<code>c</code>都是<code>尾调用</code>。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> a = <span class="function"><span class="params">()</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">let</span> result = b();</span><br><span class="line">  <span class="keyword">return</span> result;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>对于这段代码，有文章指出<code>b</code>并不是<code>尾调用</code>，即便它与<code>const a = () =&gt; b()</code>是等价的，而后者显然是尾调用。这就涉及到定义的问题了，我觉得不必过于纠结，<code>尾调用</code>的真正目的是为了进行优化，防止栈溢出，我测试了下支持<code>尾调用</code>的 safari 浏览器，在严格模式下用类似的代码执行一段递归函数，结果是不会导致栈溢出，所以 safari 对这种形式的代码做了优化。</p>
<h1 id="尾递归"><a href="#尾递归" class="headerlink" title="尾递归"></a>尾递归</h1><p>现在就轮到本篇文章的主角——<code>尾递归</code>了，看一下下面这段简单的递归代码：<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> sum = <span class="function">(<span class="params">n</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">if</span> (n &lt;= <span class="number">1</span>) <span class="keyword">return</span> n;</span><br><span class="line">  <span class="keyword">return</span> n + sum(n<span class="number">-1</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><br>就是计算从1到n的整数的和，显然这段代码并不是<code>尾递归</code>，因为<code>sum(n-1)</code>调用后还需要一步计算的过程，所以当n较大时就会导致栈溢出。我们可以把这段代码改为<code>尾递归</code>的形式：<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> sum = <span class="function">(<span class="params">n, prevSum = <span class="number">0</span></span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">if</span> (n &lt;= <span class="number">1</span>) <span class="keyword">return</span> n + prevSum;</span><br><span class="line">  <span class="keyword">return</span> sum(n<span class="number">-1</span>, n + prevSum)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><br>这样就是<code>尾递归</code>了，这段代码在 safari 里以严格模式运行时，不会出现栈溢出错误，因为它对<code>尾调用</code>做了优化。那有多少浏览器会做优化呢？其实在<a href="http://www.ecma-international.org/ecma-262/6.0/#sec-tail-position-calls" target="_blank" rel="noopener"> es6 的规范里</a>，就已经定义了对<code>尾调用</code>的优化，不过目前浏览器对其支持情况很不好:<br><img src="/2018/10/17/Javascript的尾递归及其优化/browserSupport.png" title="100%"><br>具体见<a href="http://kangax.github.io/compat-table/es6/#test-proper_tail_calls_%28tail_call_optimisation%29" target="_blank" rel="noopener">这里</a><br>即便将来大部分浏览器都支持<code>尾调用</code>优化了，按照 es6 的规范，也只会在严格模式下触发，这明显会很不方便。那我们把递归函数转为<code>尾递归</code>有什么用呢？其实我们可以通过一个统一的方法对<code>尾递归</code>函数进行处理，让其不再导致栈溢出。</p>
<h1 id="Trampoline"><a href="#Trampoline" class="headerlink" title="Trampoline"></a>Trampoline</h1><p><a href="https://en.wikipedia.org/wiki/Tail_call#Through_trampolining" target="_blank" rel="noopener">Trampoline</a>是对<code>尾递归</code>函数进行处理的一种技巧。我们需要先把上面的<code>sum</code>函数改造一下，再由<code>trampoline</code>函数处理即可：<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> sum0 = <span class="function">(<span class="params">n, prevSum = <span class="number">0</span></span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">if</span> (n &lt;= <span class="number">1</span>) <span class="keyword">return</span> n + prevSum;</span><br><span class="line">  <span class="keyword">return</span> <span class="function"><span class="params">()</span> =&gt;</span> sum0(n<span class="number">-1</span>, n + prevSum)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> trampoline = <span class="function"><span class="params">f</span> =&gt;</span> (...args) =&gt; &#123;</span><br><span class="line">  <span class="keyword">let</span> result = f(...args);</span><br><span class="line">  <span class="keyword">while</span> (<span class="keyword">typeof</span> result === <span class="string">'function'</span>) &#123;</span><br><span class="line">    result = result();</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> result;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> sum = trampoline(sum0);</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(sum(<span class="number">1000000</span>)); <span class="comment">// 不会栈溢出</span></span><br></pre></td></tr></table></figure><br>可以看到，这里实际上就是把原本的递归改为了迭代，这样就不会有栈溢出的问题啦。</p>
<p>当然，如果一个方法可以写成<code>尾递归</code>的形式，那它肯定也能被写成迭代的形式，但有些场景下使用递归可能会更加直观，如果它能被转为<code>尾递归</code>，你就可以直接用<code>trampoline</code>函数进行处理，或者把它改写成迭代的方法（或者等大部分浏览器支持<code>尾递归</code>优化后在严格模式下执行）</p>
<blockquote>
<h3 id="参考："><a href="#参考：" class="headerlink" title="参考："></a>参考：</h3><p><a href="https://blog.logrocket.com/using-trampolines-to-manage-large-recursive-loops-in-javascript-d8c9db095ae3" target="_blank" rel="noopener">https://blog.logrocket.com/using-trampolines-to-manage-large-recursive-loops-in-javascript-d8c9db095ae3</a><br><a href="http://2ality.com/2015/06/tail-call-optimization.html" target="_blank" rel="noopener">http://2ality.com/2015/06/tail-call-optimization.html</a><br><a href="https://www.zhihu.com/question/30078697/answer/146047599" target="_blank" rel="noopener">https://www.zhihu.com/question/30078697/answer/146047599</a></p>
</blockquote>

      
    </div>
    <footer class="article-footer">
      <a data-url="http://yoursite.com/2018/10/17/Javascript的尾递归及其优化/" data-id="cjt066zfe0000e0z4wmtot6vi" class="article-share-link">Share</a>
      
      
  <ul class="article-tag-list"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/尾递归-尾调用-trampoline-递归-迭代/">尾递归,尾调用,trampoline,递归,迭代</a></li></ul>

    </footer>
  </div>
  
    
<nav id="article-nav">
  
    <a href="/2019/03/04/写了一个可以拦截ajax请求并修改返回结果的插件/" id="article-nav-newer" class="article-nav-link-wrap">
      <strong class="article-nav-caption">Newer</strong>
      <div class="article-nav-title">
        
          写了一个可以拦截ajax请求并修改返回结果的chrome插件
        
      </div>
    </a>
  
  
    <a href="/2018/09/19/深入探究immutable.js的实现机制（二）/" id="article-nav-older" class="article-nav-link-wrap">
      <strong class="article-nav-caption">Older</strong>
      <div class="article-nav-title">深入探究immutable.js的实现机制（二）</div>
    </a>
  
</nav>

  
</article>

</section>
        
          <aside id="sidebar">
  
    

  
    
  <div class="widget-wrap">
    <h3 class="widget-title">Tags</h3>
    <div class="widget">
      <ul class="tag-list"><li class="tag-list-item"><a class="tag-list-link" href="/tags/HTTPS-SSL-TLS-加密-对称加密-非对称加密-数字证书-数字签名/">HTTPS,SSL,TLS,加密,对称加密,非对称加密,数字证书,数字签名</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/JavaScript-WeakMap-ES6/">JavaScript,WeakMap,ES6</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/css-white-space-word-break-word-wrap-html/">css,white-space,word-break,word-wrap,html</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/immutable-js-immutable-JavaScript-持久化数据结构-结构共享-Vector-Trie-Clojure/">immutable.js,immutable,JavaScript,持久化数据结构,结构共享,Vector Trie,Clojure</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/immutable-js-immutable-JavaScript-持久化数据结构-结构共享-bitmap-transient-hash冲突-Vector-Trie-Clojure/">immutable.js,immutable,JavaScript,持久化数据结构,结构共享,bitmap,transient,hash冲突,Vector Trie,Clojure</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/尾递归-尾调用-trampoline-递归-迭代/">尾递归,尾调用,trampoline,递归,迭代</a></li></ul>
    </div>
  </div>


  
    
  <div class="widget-wrap">
    <h3 class="widget-title">Tag Cloud</h3>
    <div class="widget tagcloud">
      <a href="/tags/HTTPS-SSL-TLS-加密-对称加密-非对称加密-数字证书-数字签名/" style="font-size: 10px;">HTTPS,SSL,TLS,加密,对称加密,非对称加密,数字证书,数字签名</a> <a href="/tags/JavaScript-WeakMap-ES6/" style="font-size: 10px;">JavaScript,WeakMap,ES6</a> <a href="/tags/css-white-space-word-break-word-wrap-html/" style="font-size: 10px;">css,white-space,word-break,word-wrap,html</a> <a href="/tags/immutable-js-immutable-JavaScript-持久化数据结构-结构共享-Vector-Trie-Clojure/" style="font-size: 10px;">immutable.js,immutable,JavaScript,持久化数据结构,结构共享,Vector Trie,Clojure</a> <a href="/tags/immutable-js-immutable-JavaScript-持久化数据结构-结构共享-bitmap-transient-hash冲突-Vector-Trie-Clojure/" style="font-size: 10px;">immutable.js,immutable,JavaScript,持久化数据结构,结构共享,bitmap,transient,hash冲突,Vector Trie,Clojure</a> <a href="/tags/尾递归-尾调用-trampoline-递归-迭代/" style="font-size: 10px;">尾递归,尾调用,trampoline,递归,迭代</a>
    </div>
  </div>

  
    
  <div class="widget-wrap">
    <h3 class="widget-title">Archives</h3>
    <div class="widget">
      <ul class="archive-list"><li class="archive-list-item"><a class="archive-list-link" href="/archives/2019/03/">March 2019</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2018/10/">October 2018</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2018/09/">September 2018</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2018/08/">August 2018</a></li></ul>
    </div>
  </div>


  
    
  <div class="widget-wrap">
    <h3 class="widget-title">Recent Posts</h3>
    <div class="widget">
      <ul>
        
          <li>
            <a href="/2019/03/04/写了一个可以拦截ajax请求并修改返回结果的插件/">写了一个可以拦截ajax请求并修改返回结果的chrome插件</a>
          </li>
        
          <li>
            <a href="/2018/10/17/Javascript的尾递归及其优化/">Javascript的尾递归及其优化</a>
          </li>
        
          <li>
            <a href="/2018/09/19/深入探究immutable.js的实现机制（二）/">深入探究immutable.js的实现机制（二）</a>
          </li>
        
          <li>
            <a href="/2018/09/12/深入探究immutable.js的实现机制（一）/">深入探究immutable.js的实现机制（一）</a>
          </li>
        
          <li>
            <a href="/2018/09/04/彻底搞懂https的加密机制/">彻底搞懂HTTPS的加密机制</a>
          </li>
        
      </ul>
    </div>
  </div>

  
</aside>
        
      </div>
      <footer id="footer">
  
  <div class="outer">
    <div id="footer-info" class="inner">
      &copy; 2019 顾伊凡<br>
      Powered by <a href="http://hexo.io/" target="_blank">Hexo</a>
    </div>
  </div>
</footer>
    </div>
    <nav id="mobile-nav">
  
    <a href="/" class="mobile-nav-link">Home</a>
  
    <a href="/archives" class="mobile-nav-link">Archives</a>
  
</nav>
    

<script src="//ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>


  <link rel="stylesheet" href="/fancybox/jquery.fancybox.css">
  <script src="/fancybox/jquery.fancybox.pack.js"></script>


<script src="/js/script.js"></script>



  </div>
</body>
</html>